/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package edu.mit.csail.sdg.alloy4; import java.io.File; import java.io.FilenameFilter; import java.util.Locale; import java.awt.BorderLayout; import java.awt.Component; import java.awt.FileDialog; import java.awt.Frame; import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; import java.awt.event.KeyListener; import java.awt.event.KeyEvent; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.filechooser.FileFilter; import static javax.swing.JOptionPane.YES_NO_OPTION; import static javax.swing.JOptionPane.QUESTION_MESSAGE; import static javax.swing.JOptionPane.WARNING_MESSAGE; import static javax.swing.JOptionPane.ERROR_MESSAGE; /** Graphical dialog methods for asking the user some questions. * * <p><b>Thread Safety:</b> Can be called only by the AWT event thread. */ public final class OurDialog { /** The constructor is private, since this utility class never needs to be instantiated. */ private OurDialog() { } /** Helper method for constructing an always-on-top modal dialog. */ private static Object show(String title, int type, Object message, Object[] options, Object initialOption) { if (options == null) { options = new Object[]{"Ok"}; initialOption = "Ok"; } JOptionPane p = new JOptionPane(message, type, JOptionPane.DEFAULT_OPTION, null, options, initialOption); p.setInitialValue(initialOption); JDialog d = p.createDialog(null, title); p.selectInitialValue(); d.setAlwaysOnTop(true); d.setVisible(true); d.dispose(); return p.getValue(); } /** Popup the given informative message, then ask the user to click Close to close it. */ public static void showmsg(String title, Object... msg) { JButton dismiss = new JButton(Util.onMac() ? "Dismiss" : "Close"); Object[] objs = new Object[msg.length + 1]; System.arraycopy(msg, 0, objs, 0, msg.length); objs[objs.length - 1] = OurUtil.makeH(null, dismiss, null); JOptionPane about = new JOptionPane(objs, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[]{}); JDialog dialog = about.createDialog(null, title); dismiss.addActionListener(Runner.createDispose(dialog)); dialog.setAlwaysOnTop(true); dialog.setVisible(true); dialog.dispose(); } /** Popup the given error message. */ public static void alert(Object message) { show("Error", ERROR_MESSAGE, message, null, null); } /** Popup the given error message, then terminate the program. */ public static void fatal(Object message) { try { show("Fatal Error", ERROR_MESSAGE, message, null, null); } finally { System.exit(1); } } /** Ask if the user wishes to save the file, discard the file, or cancel the entire operation (default is cancel). * @return 'c' if cancel, 's' if save, 'd' if discard */ public static char askSaveDiscardCancel(String description) { description = description + " has not been saved. Do you want to"; Object ans = show( "Warning", WARNING_MESSAGE, new String[] {description, "cancel the operation, close the file without saving, or save it and close?"}, new Object[] {"Save", "Don't Save", "Cancel"}, "Cancel" ); return (ans == "Save") ? 's' : (ans == "Don't Save" ? 'd' : 'c'); } /** Ask if the user really wishes to overwrite the file (default is no). * @return true if the user wishes to overwrite the file, false if the user does not wish to overwrite the file. */ public static boolean askOverwrite(String filename) { return "Overwrite" == show("Warning: file already exists", WARNING_MESSAGE, new String[] {"The file \"" + filename + "\"", "already exists. Do you wish to overwrite it?"}, new String[] {"Overwrite", "Cancel"}, "Cancel" ); } /** This caches the result of the call to get all fonts. */ private static String[] allFonts = null; /** Returns true if a font with that name exists on the system (comparison is case-insensitive). */ public synchronized static boolean hasFont(String fontname) { if (allFonts == null) allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); for(int i = 0; i < allFonts.length; i++) if (fontname.compareToIgnoreCase(allFonts[i]) == 0) return true; return false; } /** Asks the user to choose a font; returns "" if the user cancels the request. */ public synchronized static String askFont() { if (allFonts == null) allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); JComboBox jcombo = new OurCombobox(allFonts); Object ans = show("Font", JOptionPane.INFORMATION_MESSAGE, new Object[] {"Please choose the new font:", jcombo}, new Object[] {"Ok", "Cancel"}, "Cancel" ); Object value = jcombo.getSelectedItem(); if (ans=="Ok" && (value instanceof String)) return (String)value; else return ""; } /** True if we should use AWT (instead of Swing) to display the OPEN and SAVE dialog. */ private static boolean useAWT = Util.onMac(); /** Use the platform's preferred file chooser to ask the user to select a file. * <br> Note: if it is a save operation, and the user didn't include an extension, then we'll add the extension. * @param isOpen - true means this is an Open operation; false means this is a Save operation * @param dir - the initial directory (or null if we want to use the default) * @param ext - the file extension (including "."; using lowercase letters; for example, ".als") or "" * @param description - the description for the given extension * @return null if the user didn't choose anything, otherwise it returns the selected file */ public static File askFile (boolean isOpen, String dir, final String ext, final String description) { if (dir == null) dir = Util.getCurrentDirectory(); if (!(new File(dir).isDirectory())) dir = System.getProperty("user.home"); dir = Util.canon(dir); String ans; if (useAWT) { Frame parent = new Frame("Alloy File Dialog"); // this window is unused and not shown; needed by FileDialog and nothing more FileDialog open = new FileDialog(parent, isOpen ? "Open..." : "Save..."); open.setAlwaysOnTop(true); open.setMode(isOpen ? FileDialog.LOAD : FileDialog.SAVE); open.setDirectory(dir); if (ext.length()>0) open.setFilenameFilter(new FilenameFilter() { public boolean accept(File dir, String name) { return name.toLowerCase(Locale.US).endsWith(ext); } }); open.setVisible(true); // This method blocks until the user either chooses something or cancels the dialog. parent.dispose(); if (open.getFile() == null) return null; else ans = open.getDirectory() + File.separatorChar + open.getFile(); } else { try { JFileChooser open = new JFileChooser(dir) { private static final long serialVersionUID = 0; public JDialog createDialog(Component parent) throws HeadlessException { JDialog dialog = super.createDialog(null); dialog.setAlwaysOnTop(true); return dialog; } }; open.setDialogTitle(isOpen ? "Open..." : "Save..."); open.setApproveButtonText(isOpen ? "Open" : "Save"); open.setDialogType(isOpen ? JFileChooser.OPEN_DIALOG : JFileChooser.SAVE_DIALOG); if (ext.length()>0) open.setFileFilter(new FileFilter() { public boolean accept(File file) { return !file.isFile() || file.getPath().toLowerCase(Locale.US).endsWith(ext); } public String getDescription() { return description; } }); if (open.showDialog(null, null) != JFileChooser.APPROVE_OPTION || open.getSelectedFile() == null) return null; ans = open.getSelectedFile().getPath(); } catch(Exception ex) { // Some combination of Windows version and JDK version will trigger this failure. // In such a case, we'll fall back to using the "AWT" file open dialog useAWT = true; return askFile(isOpen, dir, ext, description); } } if (!isOpen) { int lastSlash = ans.lastIndexOf(File.separatorChar); int lastDot = (lastSlash>=0) ? ans.indexOf('.', lastSlash) : ans.indexOf('.'); if (lastDot < 0) ans = ans + ext; } return new File(Util.canon(ans)); } /** Display "msg" in a modal dialog window, and ask the user to choose "yes" versus "no" (default is "no"). */ public static boolean yesno(Object msg, String yes, String no) { return show("Question", WARNING_MESSAGE, msg, new Object[]{yes, no}, no) == yes; } /** Display "msg" in a modal dialog window, and ask the user to choose "Yes" versus "No" (default is "no"). */ public static boolean yesno(Object msg) { return yesno(msg, "Yes", "No"); } /** Display a modal dialog window containing the "objects"; returns true iff the user clicks Ok. */ public static boolean getInput(String title, Object... objects) { // If there is a JTextField or a JTextArea here, then let the first JTextField or JTextArea be the initially focused widget Object main = "Ok"; for(Object obj: objects) if (obj instanceof JTextField || obj instanceof JTextArea) { main = obj; break; } // Construct the dialog panel final JOptionPane pane = new JOptionPane(objects, QUESTION_MESSAGE, YES_NO_OPTION, null, new Object[]{"Ok", "Cancel"}, main); final JDialog dialog = pane.createDialog(null, title); // For each JTextField and JCheckBox, add a KeyListener that detects VK_ENTER and treat it as if the user clicked OK for(Object obj: objects) if (obj instanceof JTextField || obj instanceof JCheckBox) { ((JComponent)obj).addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { if (e.getKeyCode()==KeyEvent.VK_ENTER) { pane.setValue("Ok"); dialog.dispose(); } } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { } }); } dialog.setAlwaysOnTop(true); dialog.setVisible(true); // This method blocks until the user either chooses something or cancels the dialog. dialog.dispose(); return pane.getValue() == "Ok"; } /** Display a simple non-modal window showing some text. */ public static JFrame showtext(String title, String text) { JFrame window = new JFrame(title); JButton done = new JButton("Close"); done.addActionListener(Runner.createDispose(window)); JScrollPane scrollPane = OurUtil.scrollpane(OurUtil.textarea(text, 20, 60, false, false)); window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); window.getContentPane().setLayout(new BorderLayout()); window.getContentPane().add(scrollPane, BorderLayout.CENTER); window.getContentPane().add(done, BorderLayout.SOUTH); window.pack(); window.setSize(500, 500); window.setLocationRelativeTo(null); window.setVisible(true); return window; } }